package com.itjeffrey.autocode.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itjeffrey.autocode.bo.LoginInfoBO;
import com.itjeffrey.autocode.bo.req.RegisterBO;
import com.itjeffrey.autocode.common.BaseCommon;
import com.itjeffrey.autocode.common.Result;
import com.itjeffrey.autocode.constant.SysConstant;
import com.itjeffrey.autocode.entity.*;
import com.itjeffrey.autocode.enums.ExceptionEnum;
import com.itjeffrey.autocode.exception.ACException;
import com.itjeffrey.autocode.mapper.LoginInfoMapper;
import com.itjeffrey.autocode.mapper.UserInfoMapper;
import com.itjeffrey.autocode.service.LoginService;
import com.itjeffrey.autocode.security.JwtTokenUtil;
import com.itjeffrey.autocode.security.JwtUserDetails;
import com.itjeffrey.autocode.bo.req.ModPwdBO;
import com.itjeffrey.autocode.bo.resp.TokenBO;
import com.itjeffrey.autocode.service.RoleInfoTbService;
import com.itjeffrey.autocode.util.ACBaseUtil;
import com.itjeffrey.autocode.util.DateUtil;
import com.itjeffrey.autocode.websocket.WebSocketServerContainer;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * @From: Jeffrey
 * @Date: 2020/11/30
 */
@Service
public class LoginServiceImpl extends ServiceImpl<LoginInfoMapper, LoginInfoEntity> implements LoginService {

    private Logger log = LoggerFactory.getLogger(getClass());

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private AuthenticationManager authenticationManager;
    @Resource
    private LoginInfoMapper loginInfoMapper;
    @Resource
    private UserInfoMapper userInfoMapper;
    @Resource
    private BaseCommon baseCommon;
    @Resource
    private PasswordEncoder bCryptPasswordEncoder;
    @Resource
    private RoleInfoTbService roleInfoTbService;

    @Override
    public Result login(String username, String password) {
        //校验
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            throw new ACException(ExceptionEnum.USERNAME_OR_PASSWORD_EXISTS_NULL);
        }
        log.debug("登录信息：" + username + " -> " + password);
        //用户验证
        Authentication authentication = null;
        try {
            //该方法会去调用userDetailsService.loadUserByUsername()去验证用户名和密码，如果正确，则存储该用户名密码到“security 的 context中”
            authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username,
                    password));
        } catch (DisabledException | BadCredentialsException e) {
            throw new ACException(ExceptionEnum.LOGIN_FAIL);
        }
        log.debug("authentication: " + authentication); //Principal主体即为UserDetails
        //存储认证信息
        SecurityContextHolder.getContext().setAuthentication(authentication);
        //生成token
        Map<String, String> tokenMap = jwtTokenUtil.generateToken(new JwtUserDetails(username, password, null));
        log.debug("访问token: [{}] ", tokenMap.get("token"));
        log.debug("刷新token: [{}]", tokenMap.get("refreshToken"));
        //维护用户登录Token
        BaseCommon.tokenMap.put(username, tokenMap.get("token"));
        log.debug("[{}]登录成功！", username);
        //返回
        return new Result(tokenMap);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result modPwd(ModPwdBO modPwdBO) {
        log.debug("[{}]正在修改密码...", modPwdBO.getUserName());
        //校验
        if (modPwdBO == null || StringUtils.isEmpty(modPwdBO.getUserName()) || StringUtils.isEmpty(modPwdBO.getOldPwd
                ()) ||
                StringUtils.isEmpty(modPwdBO.getNewPwd())) {
            throw new ACException(ExceptionEnum.PARAM_EXISTS_NULL);
        }
        if (!modPwdBO.getNewPwd().equals(modPwdBO.getAckNewPwd())) {
            throw new ACException(ExceptionEnum.ACKPWD_FAIL);
        }
        LoginInfoEntity loginInfoEntity = loginInfoMapper.selectById(modPwdBO.getUserName());
        if (loginInfoEntity == null) {
            throw new ACException(ExceptionEnum.LOGIN_INFO_NOT_EXISTS);
        }
        if (!bCryptPasswordEncoder.matches(modPwdBO.getOldPwd(), loginInfoEntity.getLoginPwd())) {
            throw new ACException(ExceptionEnum.OLDPWD_INPUT_ERROR);
        }
        //修改密码
        LoginInfoEntity entity = new LoginInfoEntity();
        entity.setLoginName(modPwdBO.getUserName());
        entity.setLoginPwd(bCryptPasswordEncoder.encode(modPwdBO.getNewPwd()));
        int update = loginInfoMapper.updateById(entity);
        if (update != 1) {
            throw new ACException(ExceptionEnum.DATABASE_OPERATE_FAILURE);
        }
        //重新初始化用户登录信息loginInfoMap
        baseCommon.initLoginInfo();
        log.debug("[{}]密码修改成功！", modPwdBO.getUserName());
        //返回结果
        return Result.ok();
    }

    /**
     * 只有当访问token过期，刷新token未过期才可以获取新的访问token
     *
     * @param request
     * @param response
     * @return
     */
    @Override
    public Result refreshToken(HttpServletRequest request, HttpServletResponse response) {
        TokenBO tokenBO = new TokenBO();
        String token = request.getHeader("Authentication"); //访问token
        String refreshToken = request.getHeader("RefreshToken"); //刷新token
        log.debug("开始刷新Token，入参：accessToken->[{}]，refreshToken->[{}]", token, refreshToken);
        if (StringUtils.isEmpty(token) || StringUtils.isEmpty(refreshToken)) {
            throw new ACException(ExceptionEnum.PARAM_EXISTS_NULL);
        }
        if (!jwtTokenUtil.isTokenExpired(token)) { //访问token未过期，禁止刷新
            throw new ACException(ExceptionEnum.ACCESS_TOKEN_UN_EXPIRE);
        }
        if (!jwtTokenUtil.isTokenExpired(refreshToken)) { //访问token过期，刷新token未过期
            //刷新访问token
            String accessToken = jwtTokenUtil.refreshAccessToken(refreshToken);
            log.debug("访问Token已更新！-> [{}]", accessToken);
            if (accessToken == null) {
                throw new ACException(ExceptionEnum.ACCESS_TOKEN_FAIL);
            }
            tokenBO.setToken(accessToken);
            return Result.fail(528, accessToken);
        } else { //访问token过期，刷新token过期
            //更新访问token和刷新token
            Map<String, String> tokenMap = jwtTokenUtil.refreshToken(token);
            tokenBO.setToken(tokenMap.get("token"));
            tokenBO.setRefreshToken(tokenMap.get("refreshToken"));
            log.debug("访问Token已更新！-> [{}]", tokenMap.get("token"));
            log.debug("刷新Token已更新！-> [{}]", tokenMap.get("refreshToken"));
            return Result.fail(528, null, tokenMap);
        }
    }

    @Override
    public Result logout(HttpServletRequest request, HttpServletResponse response) {
        String username;
        try {
            String token = request.getHeader(jwtTokenUtil.getHeader());
            username = jwtTokenUtil.getUsernameFromToken(token);
        } catch (Exception e) {
            throw new ACException(ExceptionEnum.PARAM_ERROR);
        }
        //登出时将系统维护的用户登录信息tokenMap清除
        if (StringUtils.isNotBlank(username) && BaseCommon.tokenMap.containsKey(username)) {
            BaseCommon.tokenMap.remove(username);
        }
        Assert.isNull(BaseCommon.tokenMap.get(username), "用户登录信息未清除！");
        //移除指定IP下所有的websocketserver
        WebSocketServerContainer.removeBaseSocketServerMap(ACBaseUtil.getIp(request));
        log.debug("[{}]登出成功！", username);
        return Result.ok("登出成功!");
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Result register(RegisterBO registerBO) {
        log.info("注册入参：{}", registerBO.toString());
        //校验
        if (StringUtils.isBlank(registerBO.getLoginname())) {
            throw new ACException(ExceptionEnum.SOME_PARAM_EXISTS_NULL, "账号");
        }
        if (StringUtils.isBlank(registerBO.getLoginpwd())) {
            throw new ACException(ExceptionEnum.SOME_PARAM_EXISTS_NULL, "密码");
        }
        if (StringUtils.isBlank(registerBO.getUsername())) {
            throw new ACException(ExceptionEnum.SOME_PARAM_EXISTS_NULL, "用户名");
        }

        //查询指定用户名是否存在
        UserInfoEntity userentity = userInfoMapper.selectById(registerBO.getUsername());
        if (userentity != null) {
            throw new ACException(ExceptionEnum.SOME_PARAM_HAS_EXISTS, "用户名");
        }
        //查询指定账号是否存在
        LoginInfoEntity loginentity = loginInfoMapper.selectById(registerBO.getLoginname());
        if (loginentity != null) {
            throw new ACException(ExceptionEnum.SOME_PARAM_HAS_EXISTS, "账号");
        }

        //构建用户表入参
        UserInfoEntity userInfoEntity = new UserInfoEntity();
        buildUserInfoEntity(registerBO, userInfoEntity);
        //用户表入库
        int insert = userInfoMapper.insert(userInfoEntity);
        if (insert != 1) {
            throw new ACException(ExceptionEnum.DATABASE_OPERATE_FAILURE);
        }
        //登录信息表入库
        LoginInfoEntity loginInfoEntity = new LoginInfoEntity();
        loginInfoEntity.setLoginName(registerBO.getLoginname());
        loginInfoEntity.setLoginPwd(bCryptPasswordEncoder.encode(registerBO.getLoginpwd()));
        loginInfoEntity.setUserName(registerBO.getUsername());
        int loginInfoInsert = loginInfoMapper.insert(loginInfoEntity);
        if (loginInfoInsert != 1) {
            throw new ACException(ExceptionEnum.DATABASE_OPERATE_FAILURE);
        }
        BaseCommon.loginInfoMap.put(registerBO.getLoginname(), new LoginInfoBO(registerBO.getLoginname(),
                bCryptPasswordEncoder.encode(registerBO.getLoginpwd()), null));
        //给新注册的用户初始化角色
        initUserRoleRes(registerBO.getUsername());
        return Result.ok();
    }

    @Override
    public List<LoginInfoEntity> queryAllLoginInfo(String username) {
        if (StringUtils.isBlank(username)) {
            throw new ACException(ExceptionEnum.PARAM_EXISTS_NULL);
        }
        LambdaQueryWrapper<LoginInfoEntity> wrapper = new QueryWrapper<LoginInfoEntity>().lambda();
        wrapper.eq(LoginInfoEntity::getUserName, username);
        return loginInfoMapper.selectList(wrapper);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteByUsername(String username) {
        if (StringUtils.isBlank(username)) {
            throw new ACException(ExceptionEnum.PARAM_EXISTS_NULL);
        }
        LambdaUpdateWrapper<LoginInfoEntity> wrapper = new UpdateWrapper<LoginInfoEntity>().lambda();
        wrapper.eq(LoginInfoEntity::getUserName, username);
        int delete = loginInfoMapper.delete(wrapper);
        //清除登录信息相关的缓存，TokenMap, LoginInfoMap
        List<LoginInfoEntity> loginInfoEntities = this.queryAllLoginInfo(username);
        int count = 0;
        for (LoginInfoEntity loginInfoEntity : loginInfoEntities) {
            String key = loginInfoEntity.getLoginName();
            BaseCommon.tokenMap.remove(key);
            BaseCommon.loginInfoMap.remove(key);
            count++;
        }
        log.info("用户[{}]关联的{}个登录信息及{]个相关缓存已清除！", username, delete, count);
        return delete;
    }

    @Override
    public LoginInfoEntity queryOne(String loginname) {
        return loginInfoMapper.selectById(loginname);
    }

    /**
     * 新用户默认角色
     *
     * @param username
     */
    private void initUserRoleRes(String username) {
        //用户角色中间表
        UserRoleEntity userRoleEntity = new UserRoleEntity();
        userRoleEntity.setRoleName(SysConstant.NEW_USER_ROLE_NAME);
        userRoleEntity.setUserName(username);
        roleInfoTbService.addUserRole(userRoleEntity);
    }

    /**
     * 构建注册入参
     *
     * @param registerBO
     * @param userInfoEntity
     */
    private void buildUserInfoEntity(RegisterBO registerBO, UserInfoEntity userInfoEntity) {
        BeanUtils.copyProperties(registerBO, userInfoEntity);
        userInfoEntity.setEnabled(true);
        userInfoEntity.setCreateTime(DateUtil.getCurTime());
        Date birthday = registerBO.getBirthday();
        if (birthday != null) {
            userInfoEntity.setAge(DateUtil.dateToAge(birthday));
        }
    }
}
